using System.Text;
using System.Runtime.InteropServices;
using static Program;

public class NaturalStringComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        return StrCmpLogicalW(x, y);
    }

    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
    private static extern int StrCmpLogicalW(string psz1, string psz2);
}

class M3U8Downloader
{
    private readonly string M3u8Url;
    private readonly string OutputPath;
    private readonly string TempDir;
    private readonly int MaxThreads;
    private readonly bool Overwrite;
    private readonly bool SkipAdvert;
    private int AdvertCount = 0;    //广告片段数量
    private int SkipOpening;
    private readonly VideoFileFormat VideoFormat;
    private readonly HttpClient _httpClient;
    private List<TsSegment> TsSegments = [];
    private readonly StringBuilder M3u8Content = new();
    private Decryptor? decryptor = null;

    public class TsSegment
    {
        public string url { get; set; }
        public bool download { get; set; }
    }

    public enum VideoFileFormat
    {
        M3U8AndTS,
        MP4
    }

    [Flags]
    private enum MoveFileFlags
    {
        MOVEFILE_REPLACE_EXISTING = 0x00000001,
        MOVEFILE_COPY_ALLOWED = 0x00000002,
        MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004,
        MOVEFILE_WRITE_THROUGH = 0x00000008,
        MOVEFILE_CREATE_HARDLINK = 0x00000010,
        MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020
    }

    public delegate void SegmentPrepare(ref List<TsSegment> ts);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags);

    public M3U8Downloader(string m3u8Url, string outputPath, VideoFileFormat outputFormat, bool overwrite, bool skipAdvert, int maxThreads = 16, int skipOpening = 0)
    {
        M3u8Url = m3u8Url;
        OutputPath = outputPath;
        VideoFormat = outputFormat;
        Overwrite = overwrite;
        SkipAdvert = skipAdvert;
        MaxThreads = maxThreads;
        SkipOpening = skipOpening;
        _httpClient = new HttpClient();

        // 设置输出目录
        if (outputFormat == VideoFileFormat.M3U8AndTS)
        {
            Directory.CreateDirectory(outputPath);
            TempDir = outputPath; // 直接使用输出目录
        }
        else
        {
            Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
            TempDir = Path.Combine(Path.GetTempPath(), $"m3u8_downloads_{Guid.NewGuid()}");
            Directory.CreateDirectory(TempDir);
        }
    }

    public async Task DownloadAsync(SegmentPrepare Notify)
    {
        try
        {
            // 1.下载并解析M3U8文件（支持多级）
            try
            {
                ConsoleMessage("正在解析M3U8文件...");
                await ParseM3U8Async(M3u8Url);
                Notify(ref TsSegments);
                ConsoleMessage($"找到 {TsSegments.Count} 个TS片段");
            }
            catch (Exception ex)
            {
                ConsoleMessage($"M3U8网址解释失败: {ex.Message}");
            }

            // 2.保存m3u8文件
            if (VideoFormat == VideoFileFormat.M3U8AndTS)
            {
                ConsoleMessage("生成M3U8播放列表...");
                string playlist = Path.Combine(OutputPath, "0.playlist.m3u8");
                if (File.Exists(playlist))
                    File.Delete(playlist);
                await File.AppendAllTextAsync(playlist, M3u8Content.ToString());
                ConsoleMessage($"TS文件和M3U8播放列表已保存至: {OutputPath}");
            }

            // 3.多线程下载所有TS片段
            ConsoleMessage("开始下载TS片段...");
            var downloadedFiles = await DownloadTsFilesAsync();
            if (this.AdvertCount > 0)
                ConsoleMessage($"跳过 {this.AdvertCount} 个广告片段");
            if (downloadedFiles > 0)
                ConsoleMessage($"成功下载 {downloadedFiles} 个片段");

            // 4.根据输出格式处理结果
            if (VideoFormat == VideoFileFormat.MP4)
            {
                if (MergeMp4())
                    ConsoleMessage($"MP4文件已保存至: {OutputPath}");
            }
        }
        finally
        {
            // 清理临时文件（仅MP4模式）
            if (VideoFormat == VideoFileFormat.MP4 && Directory.Exists(TempDir))
            {
                Directory.Delete(TempDir, true);
            }
        }
    }

    private async Task ParseM3U8Async(string m3u8Url)
    {
        string m3u8Content = await _httpClient.GetStringAsync(m3u8Url);

        // 检查是否是主播放列表（包含二级M3U8）
        if (m3u8Content.Contains("#EXT-X-STREAM-INF"))
        {
            ConsoleMessage("检测到主播放列表，正在选择最佳质量...");
            await ProcessMasterPlaylist(m3u8Content, m3u8Url);
        }
        else
        // 普通播放列表直接解析
        ParseMediaPlaylist(m3u8Content, m3u8Url);
    }

    private async Task ProcessMasterPlaylist(string masterContent, string masterUrl)
    {
        var baseUri = new Uri(masterUrl);
        var streamInfos = new List<(int bandwidth, string uri)>();
        var lines = masterContent.Split('\n');

        // 解析所有流信息
        for (int i = 0; i < lines.Length; i++)
        {
            string line = lines[i].Trim();
            if (line.StartsWith("#EXT-X-STREAM-INF:"))
            {
                // 提取带宽信息
                int bandwidth = 0;
                int bwIndex = line.IndexOf("BANDWIDTH=");
                if (bwIndex != -1)
                {
                    bwIndex += 10;
                    int endIndex = line.IndexOf(',', bwIndex);
                    if (endIndex == -1) endIndex = line.Length;
                    string bwStr = line[bwIndex..endIndex];
                    int.TryParse(bwStr, out bandwidth);
                }

                // 获取下一行的URI
                if (i + 1 < lines.Length)
                {
                    string uriLine = lines[i + 1].Trim();
                    if (!string.IsNullOrEmpty(uriLine) && !uriLine.StartsWith('#'))
                    {
                        Uri fullUri = new (baseUri, uriLine);
                        streamInfos.Add((bandwidth, fullUri.ToString()));
                    }
                }
            }
        }

        // 选择最高带宽的流
        if (streamInfos.Count == 0)
        {
            throw new Exception("主播放列表中未找到有效流");
        }

        var bestStream = streamInfos.OrderByDescending(s => s.bandwidth).First();
        ConsoleMessage($"选择带宽: {bestStream.bandwidth} bps, URL: {bestStream.uri}");

        // 递归解析二级M3U8
        await ParseM3U8Async(bestStream.uri);
    }

    private void ParseMediaPlaylist(string content, string _m3u8Url)
    {
        M3u8Content.Clear();
        var baseUri = new Uri(_m3u8Url);
        var lines = content.Split('\n');
        int segmentIndex = 1;
        bool tsBegin = false;
        bool skipByDISCONTINUITY = false;
        double timer = 0.00;

        for (int i = 0; i < lines.Length; i++)
        {
            string line = lines[i].Trim();
            if (string.IsNullOrEmpty(line)) continue;

            // 检测加密算法
            if (line.StartsWith("#EXT-X-KEY:"))
            {
                decryptor = new Decryptor(line[11..], baseUri);
                continue;
            }

            // 跳过片头
            if (line.StartsWith("#EXTINF:"))
                timer += double.Parse(line[8..^1]);
            if (SkipOpening > 0 && timer < SkipOpening && !skipByDISCONTINUITY && (line.StartsWith("#EXTINF:") || line.EndsWith(".ts")))
                continue;

            // 到达尾部退出
            if (line == "#EXT-X-ENDLIST")
            {
                M3u8Content.AppendLine(line);
                return;
            }

            if (tsBegin && SkipAdvert && line == "#EXT-X-DISCONTINUITY")
            {
                skipByDISCONTINUITY = !skipByDISCONTINUITY;
            }
            // 跳过广告片段
            if (skipByDISCONTINUITY)
            {
                if (line.EndsWith(".ts"))
                    this.AdvertCount++;
                continue;
            }

            // 处理TS文件行
            if (line.EndsWith(".ts"))
            {
                tsBegin = true;

                M3u8Content.AppendLine($"{segmentIndex}.ts");
                segmentIndex++;
                if (Uri.TryCreate(line, UriKind.RelativeOrAbsolute, out Uri uri))
                {
                    if (!uri.IsAbsoluteUri)
                    {
                        // 处理相对路径
                        if (line.StartsWith('/'))
                        {
                            // 绝对路径
                            var domain = new Uri(baseUri.GetLeftPart(UriPartial.Authority));
                            uri = new Uri(domain, uri);
                        }
                        else
                        {
                            // 相对路径
                            uri = new Uri(baseUri, uri);
                        }
                    }

                    TsSegments.Add(new TsSegment { url = uri.ToString(), download = false });
                }
            }
            else
                M3u8Content.AppendLine(line);
        }
    }

    private async Task<int> DownloadTsFilesAsync()
    {
        //var downloadedFiles = new ConcurrentBag<string>();
        var semaphore = new SemaphoreSlim(MaxThreads);
        var tasks = new List<Task>();
        //int totalCount = TsSegments.Count;
        int completed = 0;

        for (int i = 0; i < TsSegments.Count; i++)
        {
            var ts = TsSegments[i];
            string spanfile = Path.Combine(TempDir, $"{i+1}.ts");
            if (!Overwrite && File.Exists(spanfile))
            {
                //ConsoleMessage($"跳过：{spanfile}");
                ts.download = true;
                continue;
            }

            await semaphore.WaitAsync();

            tasks.Add(Task.Run(async () =>
            {
                try
                {
                    using var response = await _httpClient.GetAsync(ts.url);
                    using var stream = await response.Content.ReadAsStreamAsync();
                    if (null == decryptor)
                    {
                        using var fileStream = File.Create(spanfile);
                        await stream.CopyToAsync(fileStream);
                    }
                    else
                    {
                        byte[] buff = new byte[stream.Length];
                        stream.Read(buff, 0, buff.Length);
                        stream.Close();
                        decryptor.AES128(buff, spanfile);
                    }

                    //downloadedFiles.Add(spanfile);
                    ts.download = true;
                    ConsoleMessage($"已下载: {Path.GetFileName(spanfile)}");
                    int current = Interlocked.Increment(ref completed);
                    //ConsoleMessage($"已下载: {current}/{totalCount}");
                }
                catch (Exception ex)
                {
                    ConsoleMessage($"下载失败 {ts.url}: {ex.Message}");
                }
                finally
                {
                    semaphore.Release();
                }
            }));
        }

        await Task.WhenAll(tasks);
        return completed;
    }

    private bool MergeMp4()
    {
        string[] files = Directory.GetFiles(TempDir, "*.ts");
        if (0 == files.Length)
        {
            ConsoleMessage("没有可以合并的TS片段文件。");
            return false;
        }
        int index = 0;
        using var outputStream = File.Create(OutputPath);
        foreach (var filename in files.OrderBy(f=>f, new NaturalStringComparer()))
        {
            Console.Write($"\r正在合并文件...{(index * 100f / files.Length):F2}%");
            using var inputStream = File.OpenRead(filename);
            inputStream.CopyTo(outputStream);
            index++;
            //标记重启后删除临时文件
            MoveFileEx(filename, null, MoveFileFlags.MOVEFILE_DELAY_UNTIL_REBOOT);
        }
        ConsoleMessage("\r正在合并文件...100%      ");
        return true;
    }
}

